# 组件化开发

就是为了封装&复用

# 使用步骤

  1. 创建组件构造器
  2. 注册组件
  3. 使用组件
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 3 使用组件 -->
      <my-component></my-component>
      <my-component></my-component>
    </div>

    <script src="/lib/vue.js"></script>
    <script>
      // 1 创建组件构造器。含义如下:
      // 调用Vue.extend()创建的是一个组件构造器
      // 通常在创建组件构造器时,传入template代表我们自定义的模板,该模板就是在使用到组件的地方,要显示的HTML代码
      // 事实上,该写法在 Vue2.x的文档中几乎已经看不到了,它会直接使用下面要讲到的语法糖
      const myComponent = Vue.extend({
        template: `
            <div>
                <h2>标题</h2>
                <p>内容1</p>
                <p>内容2</p>
            </div>`,
      });

      // 2 注册组件
      Vue.component("my-component", myComponent);

      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
        },
        methods: {},
      });
    </script>
  </body>
</html>

# 全局组件

调用 Vue.component() 注册的为全局组件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 3 使用组件 -->
      <!-- 组件必须挂载在某个 Vue 实例下,否则不生效 -->
      <my-component></my-component>
      <div><my-component></my-component></div>
    </div>
    <!-- 不生效 -->
    <my-component></my-component>

    <div id="app2">
      <!-- 3 使用组件 -->
      <!-- 组件必须挂载在某个 Vue 实例下,否则不生效 -->
      <my-component></my-component>
      <div><my-component></my-component></div>
    </div>

    <script src="/lib/vue.js"></script>
    <script>
      // 1 创建组件构造器。含义如下:
      // 调用Vue.extend()创建的是一个组件构造器
      // 通常在创建组件构造器时,传入template代表我们自定义的模板,该模板就是在使用到组件的地方,要显示的HTML代码
      // 事实上,该写法在 Vue2.x的文档中几乎已经看不到了,它会直接使用下面要讲到的语法糖
      const myComponent = Vue.extend({
        template: `
            <div>
                <h2>标题</h2>
                <p>内容1</p>
                <p>内容2</p>
            </div>`,
      });

      // 2 注册组件(该方式组册的为全局组件,意味着可以在多个 Vue 实例下使用)
      // 调用 Vue.component() 是将刚才的组件构造器注册为一个全局组件,并给它起一个组件的标签名称
      Vue.component("my-component", myComponent);

      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
        },
      });

      const vm2 = new Vue({
        el: "#app2",
        data: {
          message: "Hello",
        },
      });
    </script>
  </body>
</html>

# 局部组件 🔥

开发中一般还是更多使用局部组件

注册在某一 Vue 实例中的组件为局部组件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 3 使用组件 -->
      <!-- 组件必须挂载在某个 Vue 实例下,否则不生效 -->
      <comp></comp>
      <div><comp></comp></div>
    </div>
    <!-- 不生效 -->
    <comp></comp>

    <div id="app2">
      <!-- 3 使用组件 -->
      <!-- 组件必须挂载在某个 Vue 实例下,否则不生效 -->
      <comp></comp>
      <div><comp></comp></div>
    </div>

    <script src="/lib/vue.js"></script>
    <script>
      // 1 创建组件构造器。含义如下:
      // 调用Vue.extend()创建的是一个组件构造器
      // 通常在创建组件构造器时,传入template代表我们自定义的模板,该模板就是在使用到组件的地方,要显示的HTML代码
      // 事实上,该写法在 Vue2.x的文档中几乎已经看不到了,它会直接使用下面要讲到的语法糖
      const myComponent = Vue.extend({
        template: `
            <div>
                <h2>标题</h2>
                <p>内容1</p>
                <p>内容2</p>
            </div>`,
      });

      // 2 注册组件(该方式组册的为全局组件,意味着可以在多个 Vue 实例下使用)
      // 调用 Vue.component() 是将刚才的组件构造器注册为一个全局组件,并给它起一个组件的标签名称
      //   Vue.component("my-component", myComponent);

      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
        },
        components: {
          comp: myComponent,
        },
      });

      const vm2 = new Vue({
        el: "#app2",
        data: {
          message: "Hello",
        },
      });
    </script>
  </body>
</html>

# 父组件 & 子组件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 3 使用组件 -->
      <!-- 组件必须挂载在某个 Vue 实例下,否则不生效 -->
      <my-component2></my-component2>

      <!-- my-component1并没有在 app 的 Vue 实例中注册,无法使用 -->
      <my-component1></my-component1>
    </div>

    <script src="/lib/vue.js"></script>
    <script>
      // 创建组件构造器1(子组件)
      const myComponent1 = Vue.extend({
        template: `
            <div>
                <h2>标题1</h2>
                <p>内容1</p>
            </div>`,
      });

      // 创建组件构造器2(父组件)
      const myComponent2 = Vue.extend({
        template: `
            <div>
                <h2>标题2</h2>
                <p>内容2</p>
                <my-component1></my-component1>
            </div>`,
        components: {
          "my-component1": myComponent1,
        },
      });

      // root 组件
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
        },
        components: {
          "my-component2": myComponent2,
        },
      });
    </script>
  </body>
</html>

# 组件名称大小写 🔥

定义组件名的方式有两种:

# 使用 kebab-case

Vue.component('my-component-name', { /* ... */ })

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 ``。

# 使用 PascalCase 🔥

Vue.component('MyComponentName', { /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

# 组件组册语法糖 🔥

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 3 使用组件 -->
      <!-- 组件必须挂载在某个 Vue 实例下,否则不生效 -->
      <my-component1></my-component1>
      <div><my-component2></my-component2></div>
    </div>

    <script src="/lib/vue.js"></script>
    <script>
      // 2 注册组件(该方式组册的为全局组件,意味着可以在多个 Vue 实例下使用)。底层还是调用Vue.extend()
      // 调用 Vue.component() 是将刚才的组件构造器注册为一个全局组件,并给它起一个组件的标签名称
      Vue.component("my-component1", {
        template: `
            <div>
                <h2>标题1</h2>
                <p>内容1</p>
            </div>`,
      });

      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
        },
        components: {
          "my-component2": {
            template: `
            <div>
                <h2>标题2</h2>
                <p>内容2</p>
            </div>`,
          },
        },
      });
    </script>
  </body>
</html>

# 组件模板分离 🔥

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <!-- 3 使用组件 -->
      <!-- 组件必须挂载在某个 Vue 实例下,否则不生效 -->
      <my-component1></my-component1>

      <div><my-component2></my-component2></div>
    </div>

    <!-- 方式1:script标签,type="text/x-template" -->
    <script type="text/x-template" id="myComponent1">
      <div>
          <h2>标题1</h2>
          <p>内容1</p>
      </div>
    </script>

    <!-- 方式2:template标签。推荐-->

    <template id="myComponent2">
      <div>
        <h2>标题2</h2>
        <p>内容2</p>
      </div>
    </template>
    <script src="/lib/vue.js"></script>
    <script>
      // 2 注册组件(该方式组册的为全局组件,意味着可以在多个 Vue 实例下使用)。底层还是调用Vue.extend()
      // 调用 Vue.component() 是将刚才的组件构造器注册为一个全局组件,并给它起一个组件的标签名称
      Vue.component("my-component1", {
        template: "#myComponent1",
      });

      const MyComponent2 = {
        template: "#myComponent2",
      };

      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
        },
        // components: {
        //   myComponent2: myComponent2,
        // },
        // 对象增强写法,使用时,可以使用<my-omponent2>或<MyComponent2>。注意两种命名方式使用标签时的区别
        components: {
          MyComponent2,
        },
      });
    </script>
  </body>
</html>

# 组件的数据 data 🔥

data 必须是一个返回组件中定义的实例对象function

因为组件是用来复用的,若data是一个对象,则多个组件间对data会进行数据共享!使用 function 后每个组件都有其自己的数据,互不干扰。

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>Title</title>
 </head>

 <body>
   <div id="app">
     <!-- 3 使用组件 -->
     <!-- 组件必须挂载在某个 Vue 实例下,否则不生效 -->
     <my-component1></my-component1>

     <div><my-component2></my-component2></div>
   </div>

   <!-- 方式1:script标签,type="text/x-template" -->
   <script type="text/x-template" id="myComponent1">
     <div>
         <h2>{{title}}</h2>
         <p>{{content}}</p>
     </div>
   </script>

   <!-- 方式2:template标签 -->

   <template id="myComponent2">
     <div>
       <h2>{{title}}</h2>
       <p>{{content}}</p>
     </div>
   </template>
   <script src="/lib/vue.js"></script>
   <script>
     // 2 注册组件(该方式组册的为全局组件,意味着可以在多个 Vue 实例下使用)。底层还是调用Vue.extend()
     // 调用 Vue.component() 是将刚才的组件构造器注册为一个全局组件,并给它起一个组件的标签名称
     Vue.component("my-component1", {
       template: "#myComponent1",
       data() {
         return {
           title: "标题111",
           content: "内容111",
         };
       },
     });

     const myComponent2 = {
       template: "#myComponent2",
       data() {
         return {
           title: "标题222",
           content: "内容222",
         };
       },
     };

     const vm = new Vue({
       el: "#app",
       data: {
         message: "Hello",
       },
       components: {
         "my-component2": myComponent2,
       },
     });
   </script>
 </body>
</html>

# 父子组件的通信 props & $emit 🔥

props 为 properties 缩写

场景1:父传子?比如请求后端获取的轮播图url数组,如何传递到轮播图子组件?等等。。。

场景2:子传父?比如一个页面有多个自组件构成,点击一个自组件后,父组件需要切换另一个自组件的数据,如何发事件通知?

如何进行父子组件的通信?

  • 方式1:通过 props 向子组件传递数据
  • 方式2:通过自定义事件 $emit 向父组件发送消息

# 父传子—props

props 的值有两种方式:

  • 字符串数组:数组中的字符串就是传递时的名称
  • 对象:对象可以设置传递时的类型(有类型限制),默认值等。推荐
    • type 可以是下列原生构造函数中的一个:
      • String
      • Number
      • Boolean
      • Array
      • Object
      • Date
      • Function
      • Symbol
    • type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认
    • 注意驼峰命名v-bind的名称问题,推荐使用驼峰!
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <my-component
        v-bind:c-message="message"
        :cmovies="movies"
        :author="author"
        :student="student"
      ></my-component>
      <hr />
      <my-component></my-component>
    </div>

    <template id="myComponent">
      <!-- template中只能包含一个root元素 -->
      <div>
        <div>{{cMessage}}</div>
        <ul>
          <li v-for="movie in cmovies">{{movie}}</li>
        </ul>
        <div>{{author}}</div>
        <div>{{student}}</div>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      class Person {
        constructor(firstName, lastName) {
          this.firstName = firstName;
          this.lastName = lastName;
        }
      }

      function Person2(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
      }

      // 大驼峰是为了方便注册组件时使用ES6对象语法
      const MyComponent = {
        template: "#myComponent",
        data() {
          return {};
        },
        // 1 字符串数组,不常用
        // props: ["cmessage", "cmovies"],

        // 2 对象,常用
        // 2.1 类型限制
        // props: {
        //   cmessage: [String, Number], // 多个类型
        //   cmovies: Array,
        // },

        // 2.2 带有默认值,必传值
        props: {
          // 驼峰命名时,v-bind处必须使用c-message来绑定,其他地方可以使用驼峰
          cMessage: {
            type: [String, Number], // 多个类型
            default: "Hello MyComponent",
            required: true, // 没有提供值会报错,但是还是会先显示默认值
            // 自定义验证传入的值
            validator(value) {
              console.log(value);
              // 传入的值必须为数组其中一个
              return ["Hello", "World"].indexOf(value) > -1;
            },
          },
          cmovies: {
            type: Array,
            // 新版本在 default 是 Object 或 Array 时,返回值必须是 function
            default() {
              return ["业火的向日葵", "漆黑的追踪者"];
            },
            required: true, // 没有提供值会报错,但是还是会先显示默认值
          },
          author: {
            // 自定义类型
            type: Person,
          },
          student: {
            // 自定义类型
            type: Person,
          },
        },
      };

      const author = new Person("san", "zhang");
      const student = new Person2("si", "li");

      // root 根组件
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
          movies: [
            "引爆摩天楼",
            "迷宫的十字路口",
            "月光下的魔术师",
            "沉默的十五分钟",
          ],
          author,
          student,
        },
        methods: {},
        components: {
          MyComponent,
        },
      });
    </script>
  </body>
</html>

# 子传父—$emit 发出自定义事件

  • 子组件中,通过$emit('自定义事件名'[,params])发送事件,来触发子组件上绑定的自定义事件
  • 父组件中,通过v-on监听子组件的自定义事件绑定到父组件的方法中
  • 注意驼峰命名问题
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <my-component @category-click="categoryClick"></my-component>
    </div>

    <template id="myComponent">
      <!-- template中只能包含一个root元素 -->
      <div>
        <button v-for="category in categories" @click="btnClick(category)">
          {{category.name}}
        </button>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      // 子组件
      // 大驼峰是为了方便注册组件时使用ES6对象语法
      const MyComponent = {
        template: "#myComponent",
        data() {
          return {
            categories: [
              { id: 1, name: "热门推荐" },
              { id: 2, name: "手机数码" },
              { id: 3, name: "美妆护肤" },
              { id: 4, name: "户外健身" },
            ],
          };
        },
        methods: {
          btnClick(category) {
            // 发出自定义事件,注意驼峰问题!!!
            this.$emit("category-click", category);
          },
        },
      };
      // root 父组件
      const vm = new Vue({
        el: "#app",
        components: {
          MyComponent,
        },
        methods: {
          categoryClick(category) {
            console.log("category", category);
          },
        },
      });
    </script>
  </body>
</html>

# 单向数据流 🔥

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

# v-bind & @input 实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <my-component
        :num1="number1"
        :num2="number2"
        @change-number1="changeNumber1"
        @change-number2="changeNumber2"
      ></my-component>
    </div>

    <template id="myComponent">
      <div>
        props:{{num1}} <br />
        data:{{dNum1}} <br />

        props:<input type="text" v-model="num1" />不推荐直接修改<br />
        data:<input
          type="text"
          v-model="dNum1"
        />通过data修改,但没有影响父组件中的值<br />
        props:
        <input
          type="text"
          :value="dNum1"
          @input="num1Input"
        />通过$emit修改父组件中传递给子组件的值,来间接修改props值。修改
        data,也可以直接绑定props的值<br />
        <hr />

        props:{{num2}} <br />
        data:{{dNum2}} <br />
        props:<input type="text" v-model="num2" />不推荐直接修改<br />
        data:<input
          type="text"
          v-model="dNum2"
        />通过data修改,但没有影响父组件中的值<br />
        props:
        <input
          type="text"
          :value="dNum2"
          @input="num2Input"
        />通过$emit修改父组件中传递给子组件的值,来间接修改props值。修改
        data,也可以直接绑定props的值<br />
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data() {
          return {
            number1: 1,
            number2: 2,
          };
        },
        methods: {
          changeNumber1(value) {
            this.number1 = Number.parseInt(value);
          },
          changeNumber2(value) {
            this.number2 = Number.parseInt(value);
          },
        },
        components: {
          MyComponent: {
            template: "#myComponent",
            data() {
              return {
                dNum1: this.num1,
                dNum2: this.num2,
              };
            },
            props: {
              num1: {
                type: Number,
                default: 1,
                required: true,
              },
              num2: {
                type: Number,
                default: 2,
                required: true,
              },
            },
            methods: {
              num1Input(event) {
                console.log(event.target.value);
                this.$emit("change-number1", event.target.value);

                // 注意,子组件中 data 数据不会随父组件更新了自组件的 props 后更改
                this.dNum1 = event.target.value;
              },
              num2Input(event) {
                console.log(event.target.value);
                this.$emit("change-number2", event.target.value);

                // 注意,子组件中 data 数据不会随父组件更新了自组件的 props 后更改
                this.dNum2 = event.target.value;
              },
            },
          },
        },
      });
    </script>
  </body>
</html>

每次直接修改 props 中数据控制台就会报错

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "num1"

# watch 实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <my-component
        :num1="number1"
        :num2="number2"
        @change-number1="changeNumber1"
        @change-number2="changeNumber2"
      ></my-component>
    </div>

    <template id="myComponent">
      <div>
        props:{{num1}} <br />
        data:{{dNum1}} <br />

        props:<input type="text" v-model="num1" />不推荐直接修改<br />
        data:<input
          type="text"
          v-model="dNum1"
        />通过data修改,但没有影响父组件中的值<br />
        props:
        <input
          type="text"
          v-model="dNum1"
        />通过$emit修改父组件中传递给子组件的值,来间接修改props值。修改
        data,也可以直接绑定props的值<br />
        <hr />

        props:{{num2}} <br />
        data:{{dNum2}} <br />
        props:<input type="text" v-model="num2" />不推荐直接修改<br />
        data:<input
          type="text"
          v-model="dNum2"
        />通过data修改,但没有影响父组件中的值<br />
        props:
        <input
          type="text"
          v-model="dNum2"
        />通过$emit修改父组件中传递给子组件的值,来间接修改props值。修改
        data,也可以直接绑定props的值<br />
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data() {
          return {
            number1: 1,
            number2: 2,
          };
        },
        methods: {
          changeNumber1(value) {
            this.number1 = Number.parseInt(value);
          },
          changeNumber2(value) {
            this.number2 = Number.parseInt(value);
          },
        },
        components: {
          MyComponent: {
            template: "#myComponent",
            data() {
              return {
                dNum1: this.num1,
                dNum2: this.num2,
              };
            },
            props: {
              num1: {
                type: Number,
                default: 1,
                required: true,
              },
              num2: {
                type: Number,
                default: 2,
                required: true,
              },
            },
            // watch只能监听data中数据
            watch: {
              dNum1(newValue, oldValue) {
                this.$emit("change-number1", newValue);
              },
              dNum2(newValue) {
                this.$emit("change-number2", newValue);
              },
            },
          },
        },
      });
    </script>
  </body>
</html>

# 父子组件的访问—$refs 🔥

基本上只有 $refs会使用很多,其他的基本不使用

# 父访问子—$children & $refs 🔥

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <my-component ref="a"></my-component>
      <my-component></my-component>
      <button @click="btnClick">父组件按钮</button>
    </div>

    <template id="myComponent">
      <div>我是子组件</div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello",
        },
        methods: {
          btnClick() {
            // $children,是数组,使用很少
            // console.log(this.$children);
            // this.$children.forEach((child) => {
            //   child.showMessage();
            //   console.log(child.message);
            // });
            // $refs,是对象,默认为空,只有有ref属性的才会放入该对象中
            console.log(this.$refs.a);
            console.log(this.$refs["a"]);
            this.$refs.a.showMessage();
            console.log(this.$refs.a.message);
          },
        },
        components: {
          MyComponent: {
            template: "#myComponent",
            data() {
              return {
                message: "Hello",
              };
            },
            methods: {
              showMessage() {
                console.log("showMessage");
              },
            },
          },
        },
      });
    </script>
  </body>
</html>

# 子访问父—$parent & $root

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>

  <body>
    <div id="app">
      <outer-component ref="a"></outer-component>
    </div>

    <template id="outerComponent">
      <div>
        <inner-component></inner-component>
      </div>
    </template>

    <template id="innerComponent">
      <div>
        <div>我是子组件</div>
        <button @click="btnClick">子组件按钮</button>
      </div>
    </template>

    <script src="/lib/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "Hello Vue",
        },
        components: {
          OuterComponent: {
            template: "#outerComponent",
            data() {
              return {
                message: "Hello OuterComponent",
              };
            },
            components: {
              innerComponent: {
                template: "#innerComponent",
                methods: {
                  btnClick() {
                    // 打印出的为 VueComponent。基本不会使用
                    console.log(this.$parent);
                    console.log(this.$parent.message);

                    // 打印出的为 Vue。基本不会使用,以后 Vue 实例中仅仅会放 Router 等,不会放数据,方法等
                    console.log(this.$root);
                    console.log(this.$root.message);
                  },
                },
              },
            },
          },
        },
      });
    </script>
  </body>
</html>